package ch.ethz.fcl.metrobuzz.render.od.flow_view.sankey;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import ch.ethz.fcl.metrobuzz.data.MBData;
import ch.ethz.fcl.metrobuzz.spatialindex.object2d.Rectangle;

public class SankeyNode {
	ODSankeyDiagramData data;

	String id = null;

	private float curVolume = -1;
	private int[] allVolumes = null;

	public boolean origin = false;
	public boolean hide = false;

	Map<SankeyNode, Float>[] nodeVolumeMap = null;

	// node corresponding probability map
	Map<SankeyNode, Float> nodeProbMap = null;
	Map<SankeyNode, Rectangle> nodeRecMap = null;

	Map<SankeyNode, Float> curNodeVolumeMap = null;

	Rectangle bound = null;

	@SuppressWarnings("unchecked")
	public SankeyNode(ODSankeyDiagramData data, String id, boolean origin) {
		this.data = data;
		this.id = id;
		this.origin = origin;

		allVolumes = new int[MBData.ONE_DAY_TIME - MBData.TRIP_STRAT_TIME];
		nodeVolumeMap = (HashMap<SankeyNode, Float>[]) new HashMap<?, ?>[MBData.ONE_DAY_TIME
				- MBData.TRIP_STRAT_TIME];
		for (int i = 0; i < nodeVolumeMap.length; i++)
			nodeVolumeMap[i] = new HashMap<SankeyNode, Float>();

		nodeProbMap = new LinkedHashMap<SankeyNode, Float>();
		nodeRecMap = new LinkedHashMap<SankeyNode, Rectangle>();
	}

	@SuppressWarnings("unchecked")
	public void resetNodeMap() {
		nodeVolumeMap = (HashMap<SankeyNode, Float>[]) new HashMap<?, ?>[MBData.ONE_DAY_TIME
				- MBData.TRIP_STRAT_TIME];
		for (int i = 0; i < nodeVolumeMap.length; i++)
			nodeVolumeMap[i] = new HashMap<SankeyNode, Float>();
		curNodeVolumeMap = null;

		nodeProbMap = new LinkedHashMap<SankeyNode, Float>();
		nodeRecMap = new LinkedHashMap<SankeyNode, Rectangle>();
	}

	public void resetAll() {
		resetVolume();
		resetNodeMap();
		hide = false;
	}

	public void resetVolume() {
		curVolume = -1;
		allVolumes = new int[MBData.ONE_DAY_TIME - MBData.TRIP_STRAT_TIME];
	}

	public void resetVolume(int time) {
		allVolumes[time] = 0;
	}

	public void addVolume(int time, float volume) {
		allVolumes[time] += volume;
	}

	public void putNodePair(int time, SankeyNode node, float volume) {
		nodeVolumeMap[time].put(node, volume);
	}

	public void putNodeProb(SankeyNode node, float prob) {
		nodeProbMap.put(node, prob);
	}

	public Float getNodePairVolume(int time, SankeyNode node) {
		Float volume = nodeVolumeMap[time].get(node);
		if (volume == null)
			return 0F;
		return volume;
	}

	public void calODPairBound() {
		Map<SankeyNode, Float> nvm = getCurODPairMap();

		List<SankeyNode> orderedNodeList = new ArrayList<SankeyNode>(
				nvm.keySet());
		sortByPosition(orderedNodeList);

		int totalVolume = 0;
		for (int i = 0; i < orderedNodeList.size(); i++) {
			if (orderedNodeList.get(i).hide)
				continue;
			for (int time = MBData.startTime; time < MBData.startTime
					+ MBData.period; time++)
				totalVolume += orderedNodeList.get(i).getNodePairVolume(
						time - MBData.TRIP_STRAT_TIME, this);
		}

		float nodeMapY = bound.getMaxY();
		for (SankeyNode sn : orderedNodeList) {
			Rectangle rec = null;
			float nodeInMapHeight = bound.getHeight() * nvm.get(sn)
					/ totalVolume;
			if (origin) {
				rec = new Rectangle(bound.getMaxX(),
						nodeMapY - nodeInMapHeight, bound.getMaxX(), nodeMapY);
			} else
				rec = new Rectangle(bound.getMinX(),
						nodeMapY - nodeInMapHeight, bound.getMinX(), nodeMapY);
			nodeRecMap.put(sn, rec);
			nodeMapY -= nodeInMapHeight;
		}
	}

	public Float getNodeProb(SankeyNode node) {
		Float prob = nodeProbMap.get(node);

		return prob;
	}

	public Rectangle getNodePairBound(SankeyNode node) {
		return nodeRecMap.get(node);
	}

	public Map<SankeyNode, Float> getCurODPairMap() {
		if (curNodeVolumeMap == null) {
			curNodeVolumeMap = new HashMap<SankeyNode, Float>();
			for (int time = MBData.startTime; time < MBData.startTime
					+ MBData.period; time++) {
				Map<SankeyNode, Float> nodeMap = nodeVolumeMap[time
						- MBData.TRIP_STRAT_TIME];
				for (SankeyNode node : nodeMap.keySet()) {
					if (node.hide)
						continue;

					Float volume = curNodeVolumeMap.get(node);
					if (volume == null)
						curNodeVolumeMap.put(node, nodeMap.get(node));
					else
						curNodeVolumeMap.put(node, nodeMap.get(node) + volume);
				}
			}
		}
		return curNodeVolumeMap;
	}

	public Map<SankeyNode, Float>[] getAllNodePairMap() {
		return nodeVolumeMap;
	}

	public void setBound(Rectangle rec) {
		this.bound = rec;
	}

	public Rectangle getBound() {
		return this.bound;
	}

	public String getId() {
		return id;
	}

	public int[] getAllVolume() {
		return allVolumes;
	}

	public float getVolume() {
		if (hide)
			return 0;

		if (curVolume == -1) {
			curVolume = 0;
			for (int time = MBData.startTime; time < MBData.startTime
					+ MBData.period; time++)
				curVolume += allVolumes[time - MBData.TRIP_STRAT_TIME];
		}
		return curVolume;
	}

	public static void sortByVolume(List<? extends SankeyNode> snList) {
		Collections.sort(snList, new Comparator<SankeyNode>() {
			@Override
			public int compare(SankeyNode sn1, SankeyNode sn2) {
				if (sn1.getId().equals("Others"))
					return 1;
				if (sn2.getId().equals("Others"))
					return -1;
				if (sn1.getVolume() > sn2.getVolume())
					return -1;
				else
					return 1;
			}
		});
	}

	public static void sortByPosition(List<? extends SankeyNode> snList) {
		Collections.sort(snList, new Comparator<SankeyNode>() {
			@Override
			public int compare(SankeyNode sn1, SankeyNode sn2) {
				if (sn1.getBound() == null)
					System.out.println("sn1 " + sn1.getId() + " bound is null");
				if (sn2.getBound() == null)
					System.out.println("sn2 " + sn2.getId() + " bound is null");
				if (sn1.getBound().getMinY() > sn2.getBound().getMinY())
					return -1;
				else
					return 1;
			}
		});
	}
}
